package junior.util.math;


/**
 * ported from Apache Common Math<br/>
 * 1. make sure addAsLinear always use table<br/>
 * 2. add log domain gaussian function<br/>
 * 3. simplified singleton(no blocking)
 * 
 * @author ZhangXiaoye
 *
 */
public class LogMath {

	public static final float LOG_ONE = 0;
	public static final float LOG_ZERO = -Float.MAX_VALUE;
	
	public static final float LOG_BASE = 1.0001f;
	
	private static final float NATURAL_LOG_BASE = (float) Math.log(LOG_BASE);
	private static final float INVERSED_NATURAL_LOG_BASE = 1.0f / NATURAL_LOG_BASE;
	
    private static final float MAX_LOG_VALUE = linearToLog(Double.MAX_VALUE);
    private static final float MIN_LOG_VALUE = linearToLog(Double.MIN_VALUE);
    
    private static final float[] ADD_TABLE;
    
    static{
    	final float[] table = new float[Math.min(262144, (int) -Math.rint(linearToLog(logToLinear(0.5f) - 1)))];
    	for (int index = 0; index < table.length; ++index) {
            table[index] = linearToLog(1f + (float) logToLinear(-index));
        }
    	ADD_TABLE = table;
    }
	
	/**
	 * 正态分布在测试值的概率。<b>LOG概率表示</b>，支持非常远的离群点概率。
	 * 
	 * @param v 测试值
	 * @param mean 均值
	 * @param sigma 标准差
	 * @return 正态分布在测试值的概率，LOG概率表示。
	 */
	public static float gaussian(double v, double mean, double sigma){
		double main = Math.pow(v - mean, 2) / (-2 * Math.pow(sigma, 2));
		double base = Math.log(1 / (sigma * 2.51));
		double returnValue = (main + base) * INVERSED_NATURAL_LOG_BASE;
		if (returnValue > Float.MAX_VALUE) {
            return Float.MAX_VALUE;
        } else {
            if (returnValue < -Float.MAX_VALUE) {
                return -Float.MAX_VALUE;
            } else {
                return (float) returnValue;
            }
        }
	}
	
	/**
	 * 线性值（>=0）的LOG表示
	 * 
	 * @param linearValue 线性表示
	 * @return LOG表示
	 */
	public static float linearToLog(double linearValue){
		// code copied from Sphinx-4
		double returnValue;
        if (linearValue < 0.0) {
            throw new IllegalArgumentException("linearToLog: param must be >= 0: " + linearValue);
        } else if (linearValue <= Float.MIN_VALUE) {
            return LOG_ZERO;
        } else {
            returnValue = Math.log(linearValue) * INVERSED_NATURAL_LOG_BASE;
            if (returnValue > Float.MAX_VALUE) {
                return Float.MAX_VALUE;
            } else {
                if (returnValue < -Float.MAX_VALUE) {
                    return -Float.MAX_VALUE;
                } else {
                    return (float) returnValue;
                }
            }
        }
	}
	
    /**
     * @param logVal1 被加数1，LOG表示
     * @param logVal2 被加数2，LOG表示
     * @return 和值，LOG表示
     */
    public static final float addAsLinear(float logVal1, float logVal2) {
        float logHighestValue = logVal1;
        float logDifference = logVal1 - logVal2;
        if (logDifference < 0) {
            logHighestValue = logVal2;
            logDifference = -logDifference;
        }
        int intIndex = (int) (logDifference + 0.5);
        if (intIndex < ADD_TABLE.length) {
            return logHighestValue + ADD_TABLE[intIndex];
        } else {
            return logHighestValue;
        }
    }
	
	/**
	 * @param logValue LOG表示值
	 * @return linearValue 线性表示值
	 */
	public static double logToLinear(float logValue){
        double returnValue;
        if (logValue < MIN_LOG_VALUE) {
            returnValue = 0.0;
        } else if (logValue > MAX_LOG_VALUE) {
            returnValue = Double.MAX_VALUE;
        } else {
        	if(logValue == LOG_ZERO){
        		returnValue = Math.exp(LOG_ZERO);
        	}else{
        		returnValue = Math.exp(logValue * NATURAL_LOG_BASE);
        	}
        }
        return returnValue;
	}

}
